#include "custom_controls.hpp"
#include "fmt/format.h"
#include "gui/ui_component.hpp"
#include "imgui/IconsFontAwesome4.h"
#include "imgui/imgui.h"
#include "imgui/imgui_stdlib.h"

//#define IMGUI_DEFINE_MATH_OPERATORS
#include "imgui/imgui_internal.h"

#include <cstdarg>
#include <cstdio>
#include <regex>
#include <gui/texture.hpp>
#include <gui/texture_ids.hpp>

const char* default_ok_cancel_button_text[]
{
    ICON_FA_CHECK_CIRCLE_O " 确定",
    ICON_FA_TIMES_CIRCLE_O " 取消",
};

static std::string GetTagRenderContent(const Tag &tag)
{
    return fmt::format("#{} [{}] {}", tag.tag_id, (tag.tag_level == L_PRIMARY) ? "主" : "次", tag.tag_name);
}

namespace ImGui
{
    void WindowBottomButtons(int button_count, const char* button_texts[], ButtonCallback callback, void* user_data)
    {
        IM_ASSERT(button_count > 0 && "No buttons!!");
        IM_ASSERT(button_texts != nullptr && "bad button texts!!");

        float const max_y = (ImGui::GetContentRegionAvail() + ImGui::GetCursorScreenPos() - ImGui::GetWindowPos()).y;

        SetCursorPosY(max_y - GetTextLineHeightWithSpacing() * 1.2f);
        for (int i = 0; i < button_count; ++i)
        {
            if (Button(button_texts[i]))
            {
                if (callback)
                    callback(i, user_data);
            }
            if (button_count > i + 1) SameLine();
        }
    }

    bool RenderTag(const Tag& tag, const char* button_texts[], const ButtonCallback& callback, void* user_data)
    {
        bool called = false;
        const auto str = GetTagRenderContent(tag);
        PushStyleColor(ImGuiCol_FrameBg, ImVec4(ImColor(tag.tag_color)));
        PushStyleColor(ImGuiCol_Button, 0);
        PushStyleColor(ImGuiCol_ButtonActive, 0);
        PushStyleColor(ImGuiCol_ButtonHovered, 0);
        BeginChild (GetID(str.c_str()), ImVec2(GetTagRenderWidth(tag, button_texts), GetFrameHeightWithSpacing() * 1.2f), ImGuiChildFlags_FrameStyle);
        PushStyleVar(ImGuiStyleVar_FrameRounding, 0.f);
        AlignTextToFramePadding();
        Text("%s", str.c_str());
        PushID(tag.tag_id);
        int i = 0;
        while (button_texts[i] != nullptr)
        {
            SameLine();
            if (SmallButton(button_texts[i]) && callback) { callback(i, user_data); called=true; }
            ++i;
        }
        PopID();
        PopStyleColor(4);
        PopStyleVar(1);

        EndChild();
        return called;
    }

    float GetTagRenderWidth(const Tag& tag, const char* button_texts[])
    {
        const auto fz = GetFontSize();
        const auto str = GetTagRenderContent(tag);
        float w = CalcTextSize(str.c_str()).x + fz;
        for (int i = 0; button_texts[i]; ++i)
        {
            w += (CalcTextSize(button_texts[i]).x + fz);
        }
        return w;
    }

    void Spinner(float radius, float thickness, int num_segments, const ImVec4& color)
    {
        auto window = GetCurrentWindow();
        if (window->SkipItems)
            return;

        auto& g = *GImGui;
        const auto& style = g.Style;
        auto&& pos = GetCursorPos();
        ImVec2 size{ radius * 2, radius * 2 };
        const ImRect bb{ pos, ImVec2(pos.x + size.x, pos.y + size.y) };
        ItemSize(bb);
        if (!ItemAdd(bb, 0))
            return;

        window->DrawList->PathClear();
        int start = static_cast<int>(abs(ImSin(static_cast<float>(g.Time * 1.8f)) * (num_segments - 5)));
        const float a_min = IM_PI * 2.0f * ((float)start) / (float)num_segments;
        const float a_max = IM_PI * 2.0f * ((float)num_segments - 3) / (float)num_segments;
        const auto&& centre = ImVec2(pos.x + radius, pos.y + radius);
        for (auto i = 0; i < num_segments; i++) {
            const float a = a_min + ((float)i / (float)num_segments) * (a_max - a_min);
            auto time = static_cast<float>(g.Time);
            window->DrawList->PathLineTo({ centre.x + ImCos(a + time * 8) * radius,
                                          centre.y + ImSin(a + time * 8) * radius });
        }
        window->DrawList->PathStroke(GetColorU32(color), false, thickness);
    }

    void LongHoveredTooltips(const std::function<void()>& drawCalls, double delta_time) {
        static ImGuiID s_last_hover_id{ 0xFFFFFFFF };
        static double s_last_hover_time{ 0.f };

        const auto this_id = GetHoveredID();
        const auto now_time = GetTime();

        if (s_last_hover_id != this_id) {
            s_last_hover_id = this_id;
            s_last_hover_time = GetTime();
        }

        if (IsItemHovered() && now_time - s_last_hover_time >= delta_time && drawCalls != nullptr) {
            BeginTooltip();
            drawCalls();
            EndTooltip();
        }
    }

    void AlternativeTimeEditor (double *time_store) {
        assert(time_store);

        SetNextWindowSize(ImVec2(GetFontSize() * 20, GetFontSize() * 6), ImGuiCond_Always);
        if (BeginPopupModal("时间编辑器"))
        {
            static bool show = false;
            static std::string cached_text;

            if (!show) {
                int min = static_cast<int>(*time_store * 60);

                if (min < 60)
                    cached_text = fmt::format("{}", min);
                else
                    cached_text = fmt::format("{}:{:02d}", min / 60, min % 60);

                show = true;
            }

            InputText("##时间", &cached_text, ImGuiInputTextFlags_AutoSelectAll);
            HelperMaker("输入时间，格式为:\n 分钟/小时:分钟\n例如:\n 75（75分钟）\n 1:2（1小时2分钟）", true);

            // check input
            static std::regex regex{ R"(\d+|\d+:\d+)" };
            const bool        acceptable = std::regex_match(cached_text, regex);
            BeginDisabled(!acceptable);
            if (Button(ICON_FA_CHECK_CIRCLE_O " 确定"))
            {
                // convert to time_store and close
                if (const auto pos = cached_text.find(':'); pos == std::string::npos)
                {
                    *time_store = std::stod(cached_text) / 60.0;
                }
                else
                {
                    *time_store = 1.0 * std::stod(cached_text.substr(0, pos)) + std::stod(cached_text.substr(pos + 1)) / 60.0;
                }
                show = false;
                CloseCurrentPopup();
            }
            SameLine();
            if (Button(ICON_FA_TIMES_CIRCLE_O " 取消"))
            {
                show = false;
                CloseCurrentPopup();
            }
            EndDisabled();
            EndPopup();
        }
    }

    void DrawColoredCircle (ImVec2 center_pos, float radius, const ImU32 *colors, int color_cnt)
    {
        if (color_cnt > 0)
        {
            const auto dw = GetWindowDrawList();
            const float step = 1.f / color_cnt;

            float a0 = 0;

            for (int i = 0; i < color_cnt; ++i)
            {
#define STEP_COUNT 60
                static constexpr float resolution = STEP_COUNT / (2 * IM_PI);
                static ImVec2 buffer[STEP_COUNT];

                const float a1 = a0 + step * 2.f * IM_PI;

                buffer[0]       = center_pos;
                const int    n  = ImMax(3, static_cast<int> ((a1 - a0) * resolution));
                const double da = (a1 - a0) / (n - 1);
                for (int j = 0; j < n; ++j) {
                    const double a = a0 + j * da;
                    buffer[j + 1] = ImVec2(center_pos.x + radius * (float)cos(a), center_pos.y + radius * (float)sin(a));
                }
                dw->AddConvexPolyFilled(buffer, n + 1, colors[i]);
                a0 = a1;
            }
        }
    }

    static ImVector<ImRect> s_GroupPanelLabelStack;

#ifndef IMGUI_DEFINE_MATH_OPERATORS
    inline ImVec2 operator*(const ImVec2& vec2, float scale) {
        return {vec2.x * scale, vec2.y * scale};
    }

    inline ImVec2 operator+(const ImVec2& v1, const ImVec2 &v2) {
        return { v1.x + v2.x, v1.y + v2.y };
    }

    inline ImVec2 operator-(const ImVec2& v1, const ImVec2 &v2) {
        return { v1.x - v2.x, v1.y - v2.y };
    }
#endif

    void BeginGroupPanel (const char *name, const ImVec2 &size) {
        ImGui::BeginGroup();

        auto cursorPos = ImGui::GetCursorScreenPos();
        auto itemSpacing = ImGui::GetStyle().ItemSpacing;
        ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.0f, 0.0f));
        ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 0.0f));

        auto frameHeight = ImGui::GetFrameHeight();
        ImGui::BeginGroup();

        ImVec2 effectiveSize = size;
        if (size.x < 0.0f)
            effectiveSize.x = ImGui::GetContentRegionAvail().x;
        else
            effectiveSize.x = size.x;
        ImGui::Dummy(ImVec2(effectiveSize.x, 0.0f));

        ImGui::Dummy(ImVec2(frameHeight * 0.5f, 0.0f));
        ImGui::SameLine(0.0f, 0.0f);
        ImGui::BeginGroup();
        ImGui::Dummy(ImVec2(frameHeight * 0.5f, 0.0f));
        ImGui::SameLine(0.0f, 0.0f);
        ImGui::TextUnformatted(name);
        auto labelMin = ImGui::GetItemRectMin();
        auto labelMax = ImGui::GetItemRectMax();
        ImGui::SameLine(0.0f, 0.0f);
        ImGui::Dummy(ImVec2(0.0, frameHeight + itemSpacing.y));
        ImGui::BeginGroup();

        //ImGui::GetWindowDrawList()->AddRect(labelMin, labelMax, IM_COL32(255, 0, 255, 255));

        ImGui::PopStyleVar(2);

#if IMGUI_VERSION_NUM >= 17301
        ImGui::GetCurrentWindow()->ContentRegionRect.Max.x -= frameHeight * 0.5f;
        ImGui::GetCurrentWindow()->WorkRect.Max.x          -= frameHeight * 0.5f;
        ImGui::GetCurrentWindow()->InnerRect.Max.x         -= frameHeight * 0.5f;
#else
        ImGui::GetCurrentWindow()->ContentsRegionRect.Max.x -= frameHeight * 0.5f;
#endif
        ImGui::GetCurrentWindow()->Size.x                   -= frameHeight;

        auto itemWidth = ImGui::CalcItemWidth();
        ImGui::PushItemWidth(ImMax(0.0f, itemWidth - frameHeight));

        s_GroupPanelLabelStack.push_back(ImRect(labelMin, labelMax));
    }
    void EndGroupPanel () {
        ImGui::PopItemWidth();

        auto itemSpacing = ImGui::GetStyle().ItemSpacing;

        ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.0f, 0.0f));
        ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 0.0f));

        auto frameHeight = ImGui::GetFrameHeight();

        ImGui::EndGroup();

        //ImGui::GetWindowDrawList()->AddRectFilled(ImGui::GetItemRectMin(), ImGui::GetItemRectMax(), IM_COL32(0, 255, 0, 64), 4.0f);

        ImGui::EndGroup();

        ImGui::SameLine(0.0f, 0.0f);
        ImGui::Dummy(ImVec2(frameHeight * 0.5f, 0.0f));
        ImGui::Dummy(ImVec2(0.0, frameHeight - frameHeight * 0.5f - itemSpacing.y));

        ImGui::EndGroup();

        auto itemMin = ImGui::GetItemRectMin();
        auto itemMax = ImGui::GetItemRectMax();
        //ImGui::GetWindowDrawList()->AddRectFilled(itemMin, itemMax, IM_COL32(255, 0, 0, 64), 4.0f);

        auto labelRect = s_GroupPanelLabelStack.back();
        s_GroupPanelLabelStack.pop_back();

        ImVec2 halfFrame = ImVec2(frameHeight * 0.25f, frameHeight) * 0.5f;
        ImRect frameRect = ImRect(itemMin + halfFrame, itemMax - ImVec2(halfFrame.x, 0.0f));
        labelRect.Min.x -= itemSpacing.x;
        labelRect.Max.x += itemSpacing.x;
        for (int i = 0; i < 4; ++i)
        {
            switch (i)
            {
            // left half-plane
            case 0: ImGui::PushClipRect(ImVec2(-FLT_MAX, -FLT_MAX), ImVec2(labelRect.Min.x, FLT_MAX), true); break;
            // right half-plane
            case 1: ImGui::PushClipRect(ImVec2(labelRect.Max.x, -FLT_MAX), ImVec2(FLT_MAX, FLT_MAX), true); break;
            // top
            case 2: ImGui::PushClipRect(ImVec2(labelRect.Min.x, -FLT_MAX), ImVec2(labelRect.Max.x, labelRect.Min.y), true); break;
            // bottom
            case 3: ImGui::PushClipRect(ImVec2(labelRect.Min.x, labelRect.Max.y), ImVec2(labelRect.Max.x, FLT_MAX), true); break;
            }

            ImGui::GetWindowDrawList()->AddRect(
                    frameRect.Min, frameRect.Max,
                    ImColor(ImGui::GetStyleColorVec4(ImGuiCol_Border)),
                    halfFrame.x);

            ImGui::PopClipRect();
        }

        ImGui::PopStyleVar(2);

#if IMGUI_VERSION_NUM >= 17301
        ImGui::GetCurrentWindow()->ContentRegionRect.Max.x += frameHeight * 0.5f;
        ImGui::GetCurrentWindow()->WorkRect.Max.x          += frameHeight * 0.5f;
        ImGui::GetCurrentWindow()->InnerRect.Max.x         += frameHeight * 0.5f;
#else
        ImGui::GetCurrentWindow()->ContentsRegionRect.Max.x += frameHeight * 0.5f;
#endif
        ImGui::GetCurrentWindow()->Size.x                   += frameHeight;

        ImGui::Dummy(ImVec2(0.0f, 0.0f));

        ImGui::EndGroup();
    }

    void CenteredTextV(const char *fmt, va_list args)
    {
        char buffer[4096];
        vsnprintf(buffer, sizeof(buffer), fmt, args);

        auto TextSize = CalcTextSize(buffer);
        auto WindowSize = GetContentRegionAvail() + GetCursorScreenPos() - GetWindowPos();

        auto CursorPos = (WindowSize - TextSize) / 2.f;
        SetCursorPos(CursorPos);

        TextUnformatted(buffer);
    }

    void CenteredText(const char *fmt, ...)
    {
        va_list args;
        va_start(args, fmt);
        CenteredTextV(fmt, args);
        va_end(args);
    }

    bool NumericUpDown(const char *label, int *value, const char *fmt, int step)
    {
        assert(value != nullptr && "value is null?");
        bool changed = false;
        ImGui::PushID(label);
        ImGui::TextUnformatted(label);
        ImGui::SameLine();
        if (ImGui::SmallButton("-"))
        {
            *value -= step;
            changed = true;
        }
        ImGui::SameLine();
        ImGui::SetNextItemWidth(ImGui::GetFontSize() * 4.5f);
        changed |= ImGui::InputScalar("##Value", ImGuiDataType_S32, value, nullptr, nullptr, fmt);
        ImGui::SameLine();
        if (ImGui::SmallButton("+"))
        {
            *value += step;
            changed = true;
        }
        ImGui::PopID();
        return changed;
    }

    bool NumericUpDownLoop(const char *label, int *value, int min, int max, LoopState *state, const char *fmt, int step)
    {
        assert(max >= min);
        // ensure value is in [min,max]
        bool changed = NumericUpDown(label, value, fmt, step);
        if (changed)
        {
            LoopState st = LoopState::eNormal;
            if (*value > max)
            {
                st = LoopState::eOverflow;
                *value = min;
            }
            else if (*value < min)
            {
                st = LoopState::eUnderflow;
                *value = max;
            }
            if (state)
                *state = st;
        }
        return changed;
    }

    bool SelectPriority(const char *label, int *priority)
    {
        AlignTextToFramePadding();
        std::string inner = "##";
        inner += label;
        const auto fz = GetFontSize();
        int backup = *priority;
        char preview[4] = {'P', '0' + backup, 0, 0};
        TextUnformatted(label); SameLine(0, fz * .25f);
        SetNextItemWidth(fz * 3.5f);
        if (BeginCombo(inner.c_str(), preview))
        {
            // ImGui::PushStyleColor(ImGuiCol_FrameBgActive, IM_COL32(0,0,0,0));
            for (int i = PRIORITY_MIN; i <= PRIORITY_MAX; ++i)
            {
                char text[3] {'P', '0' + (char)i, 0};
                if (ImGui::Selectable(text, i==backup, ImGuiSelectableFlags_SpanAllColumns))
                {
                    backup = i;
                }
                if (i == backup)
                    ImGui::SetItemDefaultFocus();
                SameLine();
                Image(TextureManager::Get(i+Tex_Priority_0), fz, fz);
            }
            // ImGui::PopStyleColor();
            EndCombo();
        }
        if (backup != *priority)
        {
            *priority = backup;
            return true;
        }
        return false;
    }
}


